This notebook outlines my process of tree based and Neural Network models. This notebook is dependent on the data table gameInfo generated from DataExtraction.RMD.
Packages
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
-- Attaching packages --------------------------------------------------------------------------------- tidyverse 1.3.1 --
v ggplot2 3.3.5 v purrr 0.3.4
v tibble 3.1.2 v dplyr 1.0.7
v tidyr 1.1.3 v stringr 1.4.0
v readr 1.4.0 v forcats 0.5.1
-- Conflicts ------------------------------------------------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
Warning message:
In read_python_versions_from_registry("HCU", key = "PythonCore") :
Unexpected format for PythonCore version: 3.10
library(data.table)
data.table 1.14.0 using 4 threads (see ?getDTthreads). Latest news: r-datatable.com
Attaching package: ‘data.table’
The following objects are masked from ‘package:dplyr’:
between, first, last
The following object is masked from ‘package:purrr’:
transpose
library(randomForest)
Warning: package ‘randomForest’ was built under R version 4.1.2
randomForest 4.6-14
Type rfNews() to see new features/changes/bug fixes.
Attaching package: ‘randomForest’
The following object is masked from ‘package:dplyr’:
combine
The following object is masked from ‘package:ggplot2’:
margin
library(rpart.plot)
Warning: package ‘rpart.plot’ was built under R version 4.1.2
Loading required package: rpart
Warning: package ‘rpart’ was built under R version 4.1.2
library(word2vec)
Warning: package ‘word2vec’ was built under R version 4.1.2
library(Rtsne)
Warning: package ‘Rtsne’ was built under R version 4.1.2
library(plotly)
Warning: package ‘plotly’ was built under R version 4.1.2
Attaching package: ‘plotly’
The following object is masked from ‘package:ggplot2’:
last_plot
The following object is masked from ‘package:stats’:
filter
The following object is masked from ‘package:graphics’:
layout
library(keras)
Warning: package ‘keras’ was built under R version 4.1.2
library(tfruns)
Warning: package ‘tfruns’ was built under R version 4.1.2
library(rsample)
Warning: package ‘rsample’ was built under R version 4.1.2
Loading Data from other part
load("../data/league.RDATA")
Warning message:
In read_python_versions_from_registry("HCU", key = "PythonCore") :
Unexpected format for PythonCore version: 3.10
List to Store Results
data.tree <- list(
models = list(),
plots = list(),
temp.data = list()
)
championCluster <- list(
models = list(),
plots = list(),
temp.data = list()
)
Wrangling Data
So I want to make a basic tree classifier of projected winning team comps. For now, a basic model of simple champion tags will be used.
Setting up Training / Test Data
# Setting Seed for Reproducibility
set.seed(3)
data.tree$temp.data$sample <- sample(data.tree$temp.data$gameInfo.tree$match, nrow(data.tree$temp.data$gameInfo.tree)*.7)
data.tree$temp.data$train <- data.tree$temp.data$gameInfo.tree %>%
filter(match %in% data.tree$temp.data$sample)
data.tree$temp.data$test <- data.tree$temp.data$gameInfo.tree %>%
filter(!match %in% data.tree$temp.data$sample)
Generating Random Forest
set.seed(3)
data.tree$models$teamComp_forest <- randomForest(
team_win ~ . - match,
data = data.tree$temp.data$train,
ntree = 500,
importance = TRUE,
na.action = na.omit
)
data.tree$models$teamComp_forest
Call:
randomForest(formula = team_win ~ . - match, data = data.tree$temp.data$train, ntree = 500, importance = TRUE, na.action = na.omit)
Type of random forest: classification
Number of trees: 500
No. of variables tried at each split: 3
OOB estimate of error rate: 50.08%
Confusion matrix:
1 2 class.error
1 11396 12530 0.5236981
2 11377 12438 0.4777241
importance(data.tree$models$teamComp_forest)
1 2 MeanDecreaseAccuracy MeanDecreaseGini
Assassin_1 9.9540136 -6.814106 5.1561345 332.2679
Fighter_1 17.2205799 -13.858278 5.5311721 346.1510
Marksman_1 9.5225581 -8.116976 1.7123134 293.4874
Tank_1 7.4799838 -4.724412 3.9559451 285.2230
Mage_1 16.9019553 -14.924233 2.9464378 284.1115
Support_1 12.2424356 -11.952428 1.8623555 242.4686
Assassin_2 0.3371864 -2.088303 -2.3073435 359.3653
Fighter_2 2.4827423 -4.897604 -2.7091299 425.1212
Marksman_2 3.2506393 -4.942703 -1.7623174 369.9343
Tank_2 3.0022087 -5.144555 -2.4517398 338.7178
Mage_2 1.0833946 -1.633843 -0.6288478 389.8527
Support_2 0.6855808 -5.485852 -5.9403592 288.7111
varImpPlot(data.tree$models$teamComp_forest)

Let’s compare to a simple blue side always wins classifier:
data.tree$temp.data$gameInfo.tree %>%
count(team_win) %>%
mutate(n = n/sum(n))
Well, it’s slightly better than the naive blue side win classifier but clearly the number of champions with tags isn’t a very strong predictor of team success. With the current coding, I’m fairly certain that there won’t really be a robust classifier.
Let’s try to identify clusters of champion types. # Generating Input Team Sentences
Generating Model
Pretty clearly 5 main clusters of champions each corresponding to a role. Doesn’t really help too much in determining team compositions. I could set up a KNN to verify this but it seems pretty clear cut to me.
Neural Network
Wrangle Data
data.NN <- list()
data.NN$data.temp <- championCluster$temp.data$teams %>%
select(!match)
data.NN$data.temp
Running Model - See TeamCompNN.R
Hyperparameter Tuning
runs <- tuning_run(
"TeamCompNN.R",
flags = list(
dropout = c(0.2, 0.3, 0.4, 0.5),
unit = c(8, 16, 64)
)
)
runs %>%
arrange(desc(metric_val_accuracy))
# So a dropout of .3 and 8 unit dense network seems to produce the best validation error
results
loss accuracy
0.6919282 0.5214252
Around 52% accuracy, not the best, but not bad considering the variance of league of legends.
Saving Model
save_model_tf(model, "initialNN.tf")
2021-12-21 18:54:50.959358: W tensorflow/python/util/util.cc:368] Sets are not currently considered sequences, but this may change in the future, so consider avoiding using them.
Evaluating Example Team
model %>% predict("Sett Trundle Kindred Ziggs Leona")
[,1]
[1,] 0.5169869
A very weird way to code a team comp predictor - I’ll try a different method in Part 3.
LS0tDQp0aXRsZTogIlRyZWVzIGFuZCBTdXBwb3J0IFZlY3RvciBNYWNoaW5lcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClRoaXMgbm90ZWJvb2sgb3V0bGluZXMgbXkgcHJvY2VzcyBvZiB0cmVlIGJhc2VkIGFuZCBOZXVyYWwgTmV0d29yayBtb2RlbHMuIFRoaXMgbm90ZWJvb2sgaXMgZGVwZW5kZW50IG9uIHRoZSBkYXRhIHRhYmxlIGdhbWVJbmZvIGdlbmVyYXRlZCBmcm9tIERhdGFFeHRyYWN0aW9uLlJNRC4NCg0KIyBQYWNrYWdlcw0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeSh3b3JkMnZlYykNCmxpYnJhcnkoUnRzbmUpDQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoa2VyYXMpDQpsaWJyYXJ5KHRmcnVucykNCmxpYnJhcnkocnNhbXBsZSkNCmBgYA0KDQojIExvYWRpbmcgRGF0YSBmcm9tIG90aGVyIHBhcnQNCmBgYHtyfQ0KbG9hZCgiLi4vZGF0YS9sZWFndWUuUkRBVEEiKQ0KYGBgDQoNCiMgTGlzdCB0byBTdG9yZSBSZXN1bHRzDQpgYGB7cn0NCmRhdGEudHJlZSA8LSBsaXN0KA0KICBtb2RlbHMgPSBsaXN0KCksDQogIHBsb3RzID0gbGlzdCgpLA0KICB0ZW1wLmRhdGEgPSBsaXN0KCkNCikNCmNoYW1waW9uQ2x1c3RlciA8LSBsaXN0KA0KICBtb2RlbHMgPSBsaXN0KCksDQogIHBsb3RzID0gbGlzdCgpLA0KICB0ZW1wLmRhdGEgPSBsaXN0KCkNCikNCmBgYA0KDQoNCiMgV3JhbmdsaW5nIERhdGENClNvIEkgd2FudCB0byBtYWtlIGEgYmFzaWMgdHJlZSBjbGFzc2lmaWVyIG9mIHByb2plY3RlZCB3aW5uaW5nIHRlYW0gY29tcHMuIEZvciBub3csIGEgYmFzaWMgbW9kZWwgb2Ygc2ltcGxlIGNoYW1waW9uIHRhZ3Mgd2lsbCBiZSB1c2VkLg0KYGBge3J9DQpkYXRhLnRyZWUkdGVtcC5kYXRhJGdhbWVJbmZvLnRlbXAgPC0gZ2FtZUluZm8gJT4lIA0KICBsZWZ0X2pvaW4oDQogICAgY2hhbXBpb25zLnNjcmFwZWQsDQogICAgYnkgPSBjKCJjaGFtcGlvbk5hbWUiID0gIm5hbWUiKQ0KICApICU+JSANCiAgZ3JvdXBfYnkobWF0Y2gpICU+JSANCiAgbXV0YXRlKA0KICAgIHRlYW0gPSBybGVpZCh3aW4pDQogICkgJT4lIA0KICB1bmdyb3VwKCkNCg0KZGF0YS50cmVlJHRlbXAuZGF0YSRnYW1lSW5mby50YWdzIDwtIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udGVtcCAlPiUgDQogIGdyb3VwX2J5KG1hdGNoLCB0ZWFtKSAlPiUgDQogIGNvdW50KHRhZykgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBwaXZvdF93aWRlcigNCiAgICBuYW1lc19mcm9tID0gdGFnLA0KICAgIHZhbHVlc19mcm9tID0gbg0KICApICU+JSANCiAgcGl2b3Rfd2lkZXIoKSAlPiUgDQogIHJlcGxhY2UoaXMubmEoLiksIDApIA0KDQoNCmRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSA8LSBkYXRhLnRyZWUkdGVtcC5kYXRhJGdhbWVJbmZvLnRlbXAgJT4lIA0KICBmaWx0ZXIod2luID09IFRSVUUpICU+JSANCiAgc2VsZWN0KG1hdGNoLCB0ZWFtX3dpbiA9IHRlYW0pICU+JSANCiAgZGlzdGluY3QobWF0Y2gsIC5rZWVwX2FsbCA9IFQpICU+JSANCiAgbXV0YXRlKA0KICAgIHRlYW1fd2luID0gZmFjdG9yKHRlYW1fd2luLCBsZXZlbHMgPSBjKDEsIDIpKQ0KICApICU+JSANCiAgbGVmdF9qb2luKA0KICAgIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udGFncyAlPiUgDQogICAgICBmaWx0ZXIodGVhbSA9PSAxKSAlPiUgDQogICAgICByZW5hbWVfd2l0aCgNCiAgICAgICAgLmZuID0gZnVuY3Rpb24oeCl7DQogICAgICAgICAgDQogICAgICAgICAgcGFzdGUwKHgsICJfMSIpICU+JSANCiAgICAgICAgICAgIHJldHVybigpDQogICAgICAgICAgDQogICAgICAgIH0sDQogICAgICAgIC5jb2xzID0gMzo4DQogICAgICApICU+JSANCiAgICAgIHNlbGVjdCghdGVhbSksDQogICAgYnkgPSAibWF0Y2giDQogICkgJT4lIA0KICBsZWZ0X2pvaW4oDQogICAgZGF0YS50cmVlJHRlbXAuZGF0YSRnYW1lSW5mby50YWdzICU+JSANCiAgICAgIGZpbHRlcih0ZWFtID09IDIpICU+JSANCiAgICAgIHJlbmFtZV93aXRoKA0KICAgICAgICAuZm4gPSBmdW5jdGlvbih4KXsNCiAgICAgICAgICANCiAgICAgICAgICBwYXN0ZTAoeCwgIl8yIikgJT4lIA0KICAgICAgICAgICAgcmV0dXJuKCkNCiAgICAgICAgICANCiAgICAgICAgfSwNCiAgICAgICAgLmNvbHMgPSAzOjgNCiAgICAgICkgJT4lIA0KICAgICAgc2VsZWN0KCF0ZWFtKSwNCiAgICBieSA9ICJtYXRjaCINCiAgKSAlPiUgDQogIG11dGF0ZV9pZihpcy5pbnRlZ2VyLCBhcy5mYWN0b3IpDQoNCmRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZQ0KYGBgDQoNCiMgU2V0dGluZyB1cCBUcmFpbmluZyAvIFRlc3QgRGF0YQ0KYGBge3J9DQojIFNldHRpbmcgU2VlZCBmb3IgUmVwcm9kdWNpYmlsaXR5DQpzZXQuc2VlZCgzKQ0KIyBOZXh0IHRpbWUgdXNlIHJzYW1wbGUgDQpkYXRhLnRyZWUkdGVtcC5kYXRhJHNhbXBsZSA8LSBzYW1wbGUoZGF0YS50cmVlJHRlbXAuZGF0YSRnYW1lSW5mby50cmVlJG1hdGNoLCBucm93KGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSkqLjcpDQpkYXRhLnRyZWUkdGVtcC5kYXRhJHRyYWluIDwtIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSAlPiUgDQogIGZpbHRlcihtYXRjaCAlaW4lIGRhdGEudHJlZSR0ZW1wLmRhdGEkc2FtcGxlKQ0KZGF0YS50cmVlJHRlbXAuZGF0YSR0ZXN0IDwtIGRhdGEudHJlZSR0ZW1wLmRhdGEkZ2FtZUluZm8udHJlZSAlPiUgDQogIGZpbHRlcighbWF0Y2ggJWluJSBkYXRhLnRyZWUkdGVtcC5kYXRhJHNhbXBsZSkNCmBgYA0KDQojIEdlbmVyYXRpbmcgUmFuZG9tIEZvcmVzdA0KYGBge3J9DQpzZXQuc2VlZCgzKQ0KZGF0YS50cmVlJG1vZGVscyR0ZWFtQ29tcF9mb3Jlc3QgPC0gcmFuZG9tRm9yZXN0KA0KICB0ZWFtX3dpbiB+IC4gLSBtYXRjaCwNCiAgZGF0YSA9IGRhdGEudHJlZSR0ZW1wLmRhdGEkdHJhaW4sDQogIG50cmVlID0gNTAwLA0KICBpbXBvcnRhbmNlID0gVFJVRSwNCiAgbmEuYWN0aW9uID0gbmEub21pdA0KKQ0KDQpkYXRhLnRyZWUkbW9kZWxzJHRlYW1Db21wX2ZvcmVzdA0KYGBgDQpgYGB7cn0NCmltcG9ydGFuY2UoZGF0YS50cmVlJG1vZGVscyR0ZWFtQ29tcF9mb3Jlc3QpDQp2YXJJbXBQbG90KGRhdGEudHJlZSRtb2RlbHMkdGVhbUNvbXBfZm9yZXN0KQ0KYGBgDQpMZXQncyBjb21wYXJlIHRvIGEgc2ltcGxlIGJsdWUgc2lkZSBhbHdheXMgd2lucyBjbGFzc2lmaWVyOg0KYGBge3J9DQpkYXRhLnRyZWUkdGVtcC5kYXRhJGdhbWVJbmZvLnRyZWUgJT4lIA0KICBjb3VudCh0ZWFtX3dpbikgJT4lIA0KICBtdXRhdGUobiA9IG4vc3VtKG4pKQ0KYGBgDQpXZWxsLCBpdCdzIHNsaWdodGx5IGJldHRlciB0aGFuIHRoZSBuYWl2ZSBibHVlIHNpZGUgd2luIGNsYXNzaWZpZXIgYnV0IGNsZWFybHkgdGhlIG51bWJlciBvZiBjaGFtcGlvbnMgd2l0aCB0YWdzIGlzbid0IGEgdmVyeSBzdHJvbmcgcHJlZGljdG9yIG9mIHRlYW0gc3VjY2Vzcy4gV2l0aCB0aGUgY3VycmVudCBjb2RpbmcsIEknbSBmYWlybHkgY2VydGFpbiB0aGF0IHRoZXJlIHdvbid0IHJlYWxseSBiZSBhIHJvYnVzdCBjbGFzc2lmaWVyLg0KDQpMZXQncyB0cnkgdG8gaWRlbnRpZnkgY2x1c3RlcnMgb2YgY2hhbXBpb24gdHlwZXMuDQojIEdlbmVyYXRpbmcgSW5wdXQgVGVhbSBTZW50ZW5jZXMgDQpgYGB7cn0NCmNoYW1waW9uQ2x1c3RlciR0ZW1wLmRhdGEkdGVhbXMgPC0gZ2FtZUluZm8gJT4lIA0KICBzZWxlY3QobWF0Y2gsIHdpbiwgY2hhbXBpb25OYW1lKSAlPiUgDQogIGdyb3VwX2J5KG1hdGNoLCB3aW4pICU+JSANCiAgbXV0YXRlKGNoYW1waW9uTnVtYmVyID0gcm93X251bWJlcigpKSAlPiUgDQogIHBpdm90X3dpZGVyKA0KICAgIG5hbWVzX2Zyb20gPSBjaGFtcGlvbk51bWJlciwNCiAgICB2YWx1ZXNfZnJvbSA9IGNoYW1waW9uTmFtZQ0KICApICU+JSANCiAgdHJhbnNtdXRlKG1hdGNoID0gbWF0Y2gsIHdpbiA9IHdpbiwgdGVhbSA9IHN0cl9jKGAxYCxgMmAsYDNgLGA0YCxgNWAsIHNlcCA9ICIgIikpICU+JSANCiAgdW5ncm91cCgpIA0KDQpjaGFtcGlvbkNsdXN0ZXIkdGVtcC5kYXRhJHRlYW1zDQp3cml0ZV9jc3YoY2hhbXBpb25DbHVzdGVyJHRlbXAuZGF0YSR0ZWFtcywgIi4uL2RhdGEvdGVhbU5hbWVzLmNzdiIpDQpgYGANCiMgR2VuZXJhdGluZyBNb2RlbA0KYGBge3J9DQpzZXQuc2VlZCgzKQ0KY2hhbXBpb25DbHVzdGVyJG1vZGVscyRubHBNb2RlbCA8LSB3b3JkMnZlYygNCiAgeCA9IGNoYW1waW9uQ2x1c3RlciR0ZW1wLmRhdGEkdGVhbXMkdGVhbSwgDQogIHR5cGUgPSAic2tpcC1ncmFtIiwgDQogIGRpbSA9IDIwLCANCiAgaXRlciA9IDE1DQopDQoNCiMgRW1iZWRkaW5nIE1hdHJpeA0KY2hhbXBpb25DbHVzdGVyJG1vZGVscyRlbWJlZGRpbmdNYXRyaXggPC0gYXMubWF0cml4KGNoYW1waW9uQ2x1c3RlciRtb2RlbHMkbmxwTW9kZWwpDQoNCiMgQXBwbHlpbmcgVFNuZSANCmNoYW1waW9uQ2x1c3RlciRtb2RlbHMkVHNuZSA8LSBSdHNuZShjaGFtcGlvbkNsdXN0ZXIkbW9kZWxzJGVtYmVkZGluZ01hdHJpeCwgcGNhID0gRkFMU0UpDQoNCmNoYW1waW9uQ2x1c3RlciRwbG90cyRtYXAgPC0gY2hhbXBpb25DbHVzdGVyJG1vZGVscyRUc25lJFkgJT4lIA0KICBhcy5kYXRhLmZyYW1lKCkgJT4lDQogIG11dGF0ZShjaGFtcGlvbiA9IHJvdy5uYW1lcyhjaGFtcGlvbkNsdXN0ZXIkbW9kZWxzJGVtYmVkZGluZ01hdHJpeCkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBWMSwgeSA9IFYyLCBsYWJlbCA9IGNoYW1waW9uKSkgKyANCiAgZ2VvbV9wb2ludCgpIA0KDQpjaGFtcGlvbkNsdXN0ZXIkcGxvdHMkbWFwIDwtIGNoYW1waW9uQ2x1c3RlciRwbG90cyRtYXAgJT4lIA0KICBnZ3Bsb3RseSgpDQoNCmNoYW1waW9uQ2x1c3RlciRwbG90cyRtYXAgDQpgYGANClByZXR0eSBjbGVhcmx5IDUgbWFpbiBjbHVzdGVycyBvZiBjaGFtcGlvbnMgZWFjaCBjb3JyZXNwb25kaW5nIHRvIGEgcm9sZS4gRG9lc24ndCByZWFsbHkgaGVscCB0b28gbXVjaCBpbiBkZXRlcm1pbmluZyB0ZWFtIGNvbXBvc2l0aW9ucy4gSSBjb3VsZCBzZXQgdXAgYSBLTk4gdG8gdmVyaWZ5IHRoaXMgYnV0IGl0IHNlZW1zIHByZXR0eSBjbGVhciBjdXQgdG8gbWUuDQoNCiMgTmV1cmFsIE5ldHdvcmsNCiMjIFdyYW5nbGUgRGF0YQ0KYGBge3J9DQpkYXRhLk5OIDwtIGxpc3QoKQ0KZGF0YS5OTiRkYXRhLnRlbXAgPC0gY2hhbXBpb25DbHVzdGVyJHRlbXAuZGF0YSR0ZWFtcyAlPiUgDQogIHNlbGVjdCghbWF0Y2gpDQogIA0KZGF0YS5OTiRkYXRhLnRlbXANCmBgYA0KDQojIFJ1bm5pbmcgTW9kZWwgLSBTZWUgVGVhbUNvbXBOTi5SDQojIyBIeXBlcnBhcmFtZXRlciBUdW5pbmcNCmBgYHtyfQ0KcnVucyA8LSB0dW5pbmdfcnVuKA0KICAiVGVhbUNvbXBOTi5SIiwNCiAgZmxhZ3MgPSBsaXN0KA0KICAgIGRyb3BvdXQgPSBjKDAuMiwgMC4zLCAwLjQsIDAuNSksDQogICAgdW5pdCA9IGMoOCwgMTYsIDY0KQ0KICApDQopDQoNCnJ1bnMgJT4lIA0KICBhcnJhbmdlKGRlc2MobWV0cmljX3ZhbF9hY2N1cmFjeSkpDQojIFNvIGEgZHJvcG91dCBvZiAuMyBhbmQgOCB1bml0IGRlbnNlIG5ldHdvcmsgc2VlbXMgdG8gcHJvZHVjZSB0aGUgYmVzdCB2YWxpZGF0aW9uIGVycm9yDQpgYGANCg0KYGBge3J9DQpyZXN1bHRzDQpgYGANCkFyb3VuZCA1MiUgYWNjdXJhY3ksIG5vdCB0aGUgYmVzdCwgYnV0IG5vdCBiYWQgY29uc2lkZXJpbmcgdGhlIHZhcmlhbmNlIG9mIGxlYWd1ZSBvZiBsZWdlbmRzLg0KDQojIFNhdmluZyBNb2RlbA0KYGBge3J9DQpzYXZlX21vZGVsX3RmKG1vZGVsLCAiaW5pdGlhbE5OLnRmIikNCmBgYA0KYGBge3IgaW5jbHVkZSA9IEZ9DQptb2RlbCA8LSBsb2FkX21vZGVsX3RmKCIuL2luaXRpYWxOTi50ZiIpDQpgYGANCg0KDQojIEV2YWx1YXRpbmcgRXhhbXBsZSBUZWFtDQpgYGB7cn0NCm1vZGVsICU+JSBwcmVkaWN0KCJTZXR0IFRydW5kbGUgS2luZHJlZCBaaWdncyBMZW9uYSIpDQpgYGANCkEgdmVyeSB3ZWlyZCB3YXkgdG8gY29kZSBhIHRlYW0gY29tcCBwcmVkaWN0b3IgLSBJJ2xsIHRyeSBhIGRpZmZlcmVudCBtZXRob2QgaW4gUGFydCAzLg0K